﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Serialization;
using Tomahawk.Runtime.Canvas.Sprite;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
using Tomahawk.Runtime.Canvas;
using Tomahawk.Runtime.Localization;
using Tomahawk.Runtime.Resources;

namespace Tomahawk.Runtime.Logic.UI.Components
{
    /// <summary>
    /// Reusable string render component
    /// </summary>
    public class TextComponent
    {

        /// <summary>
        /// Size of the text. Used to word wrap.
        /// </summary>
        private Vector2 textSize;

        /// <summary>
        /// Resource pool
        /// </summary>
        [XmlAttribute("pool")]
        public ResourceManager.ResourcePool ResourcePool;

        /// <summary>
        /// The text is word wrapped.
        /// </summary>
        [XmlAttribute("wordwrap")]
        public bool bDoesWordWrap = false;

        /// <summary>
        /// TextStyle name from where to read the configurable parameters
        /// </summary>
        [XmlAttribute("style")]
        public string StyleName;

        /// <summary>
        /// Indicate if the text shouldn't be localized
        /// </summary>
        [XmlAttribute("unlocalized")]
        public bool Unlocalized;

        /// <summary>
        /// Indicates the effect channel this image component will use
        /// </summary>
        [XmlAttribute("effectChannel")]
        public string effectChannel;

        /// <summary>
        /// Text that must be rendered
        /// </summary>
        [XmlText()]
        public string Text;

        /// <summary>
        /// Buffer to hold the localized text
        /// </summary>
        private string localizedText;

        /// <summary>
        /// Text Style definition
        /// </summary>
        private XStyleManager.TextStyle textStyle;

        /// <summary>
        /// List of sprites representing each of the text lines.
        /// </summary>
        private List<UITextSprite> sprites;

        /// <summary>
        /// Saves the greates spacing used
        /// </summary>
        private int fontSpacing = 0;

        /// <summary>
        /// Gets the configured text style
        /// </summary>
        public XStyleManager.TextStyle TextStyle
        {
            get { return this.textStyle; }
        }

        /// <summary>
        /// Size the text must adapt to.
        /// </summary>
        public Vector2 TextSize
        {
            set { textSize = value; }
        }

        /// <summary>
        /// Cosntructor.
        /// </summary>
        public TextComponent()
        {
            sprites = new List<UITextSprite>();
            this.ResourcePool = ResourceManager.ResourcePool.GameState;
        }

        private CanvasManager.CanvasLayers canvasLayer;

        /// <summary>
        /// Initialization
        /// </summary>
        public void Initialize(CanvasManager.CanvasLayers canvasLayer)
        {
            //save canvas layer for future use
            this.canvasLayer = canvasLayer;

            if (this.effectChannel == null)
            {
                this.effectChannel = "Default";
            }

            //Get the style (if none specified, use "DefaultButton")
            if ((StyleName == null) || (StyleName == "")) this.StyleName = "DefaultButton";
            textStyle = Engine.Instance.EngineConfiguration.StyleManager.GetTextStyle(this.StyleName);
            
            //if no text, use empty string
            if (this.Text == null) this.Text = "";

            //localize the text
            if (!this.Unlocalized)
            {
                Engine.Instance.LocalizationManager.GetLocalizedString(this.Text, out this.localizedText);
                Engine.Instance.LocalizationManager.LanguageChanged += new Tomahawk.Runtime.Localization.LanguageChangedDelegate(LocalizationManager_LanguageChanged);
            }
            else
            {
                this.localizedText = this.Text;
            }
            
            //create the font asset
            this.createSprite();
            
            //set the default font color
            this.SetFontColor(ref this.textStyle.DefaultColor);
            
            //set the shadow properties
            this.SetShadowProperties(this.textStyle.Shadow.Enabled,
                ref this.textStyle.ShadowColor, 
                this.textStyle.Shadow.HorizontalDisplacement, 
                this.textStyle.Shadow.VerticalDisplacement);

            //set alignment properties
            this.SetAlignment(ref this.textStyle.Alignment);

        }

        /// <summary>
        /// Handle language change
        /// </summary>
        void LocalizationManager_LanguageChanged(LocalizationManager.Language newLanguage)
        {
            this.SetText(this.Text);
        }

        /// <summary>
        /// Aligment
        /// </summary>
        private void SetAlignment(ref XStyleManager.TextAlign textAlign)
        {
            foreach (UITextSprite sprite in this.sprites)
            {
                //TODO: Only horinzontal aligment supported right now
                sprite.HAlign = textAlign.HorizontalAlign;
            }
        }

        /// <summary>
        /// Create the font, using the asset name stored in the style configuration
        /// </summary>
        private void createSprite()
        {
            if (textStyle != null)
            {
                //Create an UITextSprite
                if ((textStyle.FontAsset != null) && (textStyle.FontAsset != ""))
                {
                    // Creation of the first sprite. the text will be set later.
                    UITextSprite sprite = new UITextSprite(textStyle.FontAsset, "", this.effectChannel, this.ResourcePool);
                    sprites.Add(sprite);
                    Engine.Instance.CanvasManager.GetUISpriteRenderer(this.canvasLayer).AddPrimitive(sprite);
                }
            }
            else
            {
                Engine.Instance.DebugConsole.Warn("Cannot create TextComponent, invalid style specified: " + this.StyleName);
            }
        }

        /// <summary>
        /// Sets the text. Does the word wrapping.
        /// </summary>
        public void SetUpText()
        {
            // Font used to measure lines.
            SpriteFont font = null;
            if (sprites.Count > 0) font = sprites[0].Font;
            if (font != null)
            {
                // Number of sprites already being used.
                int numSprites = sprites.Count;

                // Index of the last sprite used by the new text. The rest of the sprites will be destroyed.
                int lastUsedSprite = 1;

                // check localizedText
                if (localizedText == null) localizedText = "";

                // Length of the string given.
                Vector2 size = font.MeasureString(localizedText);

                // If the string is shorter than a line, the text is set.
                if (!bDoesWordWrap || size.X <= textSize.X)
                {
                    sprites[0].SetText(localizedText);
                }
                else
                {
                    // The line is split in words.
                    string[] words = localizedText.Split(new char[] { ' ' });
                    List<string> lines = new List<string>();

                    string line = "";

                    // We make lines with the most words possible without surpassing the maximum line lenght.
                    foreach (string word in words)
                    {
                        string newLine = line + word + " ";
                        size = font.MeasureString(newLine);
                        if (size.X > textSize.X)
                        {
                            // New line.
                            lines.Add(line);

                            line = word + " ";
                        }
                        else
                        {
                            line = newLine;
                        }
                    }

                    // Add the last processed line.
                    lines.Add(line);

                    // Turn lines into sprites.
                    int numLines = lines.Count;
                    int i;
                    for (i = 0; i < numLines; ++i)
                    {
                        string spriteline = lines[i];
                        UITextSprite sprite = null;

                        // Reuse sprites.
                        if (i < numSprites)
                        {
                            sprite = sprites[i];
                        }

                        if (sprite != null)
                        {
                            sprite.SetText(spriteline);
                        }
                        else
                        {
                            // New sprite.
                            sprite = new UITextSprite(textStyle.FontAsset, spriteline, this.effectChannel, this.ResourcePool);
                            sprites.Add(sprite);
                            Engine.Instance.CanvasManager.GetUISpriteRenderer(this.canvasLayer).AddPrimitive(sprite);
                        }
                    }

                    lastUsedSprite = i;

                    //
                    // Copy the fist sprite parameters to the new sprites.
                    //

                    SetDepth(sprites[0].Depth);             //depth

                    XStyleManager.FontColor clr;            //color
                    clr.R = sprites[0].Color.R;
                    clr.B = sprites[0].Color.B;
                    clr.G = sprites[0].Color.G;
                    clr.A = sprites[0].Color.A;                   
                    SetFontColor(ref clr);

                    this.SetPosition(ref sprites[0].DestinationRectangle);   //position

                    this.SetVisibility(sprites[0].Visible);      //visibility

                    this.SetShadowProperties(this.textStyle.Shadow.Enabled,  //shadow
                        ref this.textStyle.ShadowColor,
                        this.textStyle.Shadow.HorizontalDisplacement,
                        this.textStyle.Shadow.VerticalDisplacement);
                    
                    this.SetAlignment(ref this.textStyle.Alignment);

                    //Alpha = sprites[0].Color.A;             //alpha

                }

                // The rest of the unused sprites are eliminated.
                for (int i = numSprites - 1; i >= lastUsedSprite; --i)
                {
                    Engine.Instance.CanvasManager.GetUISpriteRenderer(this.canvasLayer).RemovePrimitive(sprites[i]);

                    sprites.RemoveAt(i);
                }
            }
        }

        /// <summary>
        /// Changes the visibility of the component
        /// </summary>
        public void SetVisibility(bool value)
        {
            foreach (UITextSprite sprite in sprites)
            {
                if (sprite != null)
                {
                    sprite.Visible = value;
                }
            }
        }

        /// <summary>
        /// Changes the position of the component
        /// </summary>
        public void SetPosition(ref Rectangle rect)
        {
            int numLine = 0;

            foreach (UITextSprite sprite in sprites)
            {            
                if (sprite != null)
                {
                    //determine spacing
                    if (sprite.Font != null)
                    {
                        if (fontSpacing < sprite.Font.LineSpacing) fontSpacing = sprite.Font.LineSpacing;
                    }
                    int spacing = fontSpacing + this.textStyle.Margin.Vertical;
                        
                    //position sprite
                    sprite.DestinationRectangle.X = rect.X;
                    sprite.DestinationRectangle.Y = rect.Y;

                    //modify sprite origin
                    Vector2 offset = new Vector2();
                    offset.X = 0.0f;
                    offset.Y = -numLine * spacing;
                    sprite.Offset = offset;

                    ++numLine;
                }
            }
        }

        /// <summary>
        /// Changes the rotation of the component
        /// </summary>
        public void SetRotation(float rotation, ref Vector2 origin)
        {
            foreach (UITextSprite sprite in sprites)
            {
                if (sprite != null)
                {
                    sprite.Rotation = rotation;
                    sprite.Origin = origin;
                }
            }
        }

        /// <summary>
        /// Changes the font scale of the component
        /// </summary>
        public void SetFontScale(float scale)
        {
            foreach (UITextSprite sprite in sprites)
            {
                if (sprite != null)
                {
                    sprite.FontScale = scale;
                }
            }
        }

        /// <summary>
        /// Changes the depth of the component
        /// </summary
        public void SetDepth(float depth)
        {
            foreach (UITextSprite sprite in sprites)
            {
                if (sprite != null)
                {
                    sprite.Depth = depth;
                }
            }
        }

        /// <summary>
        /// Set shadow properties
        /// </summary>
        private void SetShadowProperties(bool enabled, ref XStyleManager.FontColor color, int hDisplacement, int vDisplacement)
        {
            foreach (UITextSprite sprite in sprites)
            {
                if (sprite != null)
                {
                    sprite.ShadowEnabled = enabled;
                    sprite.ShadowColor.A = color.A;
                    sprite.ShadowColor.B = color.B;
                    sprite.ShadowColor.R = color.R;
                    sprite.ShadowColor.G = color.G;
                    sprite.ShadowDisplacement.X = hDisplacement;
                    sprite.ShadowDisplacement.Y = vDisplacement;
                }
            }
        }

        /// <summary>
        /// Updates text
        /// </summary>
        public void SetText(string text)
        {
            if (!this.Unlocalized)
            {
                //set the localized text
                this.Text = text;
                Engine.Instance.LocalizationManager.GetLocalizedString(text, out this.localizedText);
            }
            else
            {
                this.Text = text;
                localizedText = text;
            }

            SetUpText();
        }

        /// <summary>
        /// Sets the font color
        /// </summary>
        public void SetFontColor(ref XStyleManager.FontColor fontColor)
        {
            foreach (UITextSprite sprite in sprites)
            {
                if (sprite != null)
                {
                    sprite.Color.R = fontColor.R;
                    sprite.Color.B = fontColor.B;
                    sprite.Color.G = fontColor.G;
                    sprite.Color.A = fontColor.A;
                }
            }
        }

        /// <summary>
        /// Returns if the component is valid (i.e. correctly initialized)
        /// </summary>
        public bool IsValid()
        {
            return (sprites[0] != null);
        }

        /// <summary>
        /// Destruction
        /// </summary>
        public void Destroy()
        {
            //unsubscribe to localization manager
            if (!this.Unlocalized)
            {
                Engine.Instance.LocalizationManager.LanguageChanged -= LocalizationManager_LanguageChanged;
            }

            foreach (UITextSprite sprite in sprites)
            {
                if (sprite != null)
                {
                    Engine.Instance.CanvasManager.GetUISpriteRenderer(this.canvasLayer).RemovePrimitive(sprite);
                }
            }

            sprites.Clear();

            textStyle = null;
        }

        /// <summary>
        /// Return wether the component is ready
        /// </summary>
        /// <returns></returns>
        public bool IsReady()
        {
            bool retVal = true;

            foreach (UITextSprite sprite in sprites)
            {
                if (sprite != null)
                {
                    if (!sprite.IsReady)
                    {
                        retVal = false;
                        break;
                    }
                }
            }

            return retVal;
        }

        /// <summary>
        /// Gets or sets the image alpha
        /// </summary>
        public byte Alpha
        {
            get
            {
                byte retVal = (byte)0;
                if (sprites.Count > 0) retVal = (sprites[0] != null) ? sprites[0].Color.A : (byte)0;
                return retVal;
            }
            set
            {
                foreach (UITextSprite sprite in sprites)
                {
                    if (sprite != null)
                    {
                        sprite.Color.A = value;
                        sprite.ShadowColor.A = value;
                    }
                }
            }
        }

        /// <summary>
        /// Get access to the font
        /// </summary>
        public SpriteFont GetFont()
        {
            if (this.sprites[0] != null)
            {
                return this.sprites[0].Font;
            }
            else return null;
        }

        /// <summary>
        /// Returns the localized string
        /// </summary>
        public string GetLocalizedText()
        {
            return this.localizedText;
        }

    }

}
